/*
 * Copyright (c) 2002 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 */
package net.sourceforge.perforce.ui.action;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.resources.RemoteFile;
import net.sourceforge.perforce.core.resources.RemoteFolder;
import net.sourceforge.perforce.ui.PerforceUIPlugin;
import net.sourceforge.perforce.ui.internal.Policy;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.actions.ActionDelegate;

/**
  * @version $Revision: 77 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
public abstract class PerforceAction extends ActionDelegate implements IObjectActionDelegate
{	
	// ---------------------------------------------------------------------------- Public Constants
	
	/** Progress with dialog. */
	public static final int PROGRESS_DIALOG = 1;
	/** Progress with budy cursor. */
	public static final int PROGRESS_BUSYCURSOR = 2;

	// --------------------------------------------------------------------------- Private Variables
	
	/** Stores the shell. */
	private Shell shell;
	/** Stores the selection. */
	private IStructuredSelection selection;

	// ------------------------------------------------------ Public Methods (IObjectActionDelegate)
	
	/** @see org.eclipse.ui.IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart) */
	public void setActivePart(IAction action, IWorkbenchPart targetPart) 
	{
		this.shell = targetPart.getSite().getShell();
	}
	
	// ------------------------------------------------------------ Public Methods (IActionDelegate)
	
	/** @see IActionDelegate#selectionChanged(action, selection) */
	public void selectionChanged(IAction action, ISelection selection) 
	{
		if (selection instanceof IStructuredSelection) 
		{
			this.selection = (IStructuredSelection) selection;
			if (action != null) 
			{
				try 
				{
					action.setEnabled(isEnabled());
				} 
				catch (PerforceException e) 
				{
					action.setEnabled(false);
					PerforceUIPlugin.log(e.getStatus());
				}
			}
		}
	}	

	// --------------------------------------------------------------------------- Protected Methods

	/** Call this method to retrieve the current selection.
	  * 
	  * @return the current selection.
	  */
	protected IStructuredSelection getSelection()
	{
		return selection;
	}
	
	/** Returns the Shell object.
	  * 
	  * @return the shell.
	  */
	protected Shell getShell()
	{
		if (shell != null) 
		{
			return shell;
		} 
		else 
		{
			return
				PerforceUIPlugin.getPlugin().getWorkbench().getActiveWorkbenchWindow().getShell();
		}
	}

	/**
	  * Convenience method for running an operation with progress and
	  * error feedback.
	  * 
	  * @param runnable  the runnable which executes the operation
	  * @param progressKind  one of PROGRESS_BUSYCURSOR or PROGRESS_DIALOG
	  */
	protected final void run(final IRunnableWithProgress runnable, int progressKind) 
	{
		final Exception[] exceptions = new Exception[] { null };
		if (progressKind == PROGRESS_BUSYCURSOR)
		{
			BusyIndicator.showWhile(Display.getCurrent(), new Runnable() 
			{
				public void run() 
				{
					try 
					{
						runnable.run(Policy.monitorFor(null));
					} 
					catch (InvocationTargetException e) 
					{
						exceptions[0] = e;
					} 
					catch (InterruptedException e) 
					{
						exceptions[0] = null;
					}
				}
			});
		}
		else
		{
			try 
			{
				new ProgressMonitorDialog(getShell()).run(true, true, runnable);
			} 
			catch (InvocationTargetException e) 
			{
				exceptions[0] = e;
			} 
			catch (InterruptedException e) 
			{
				exceptions[0] = null;
			}
		}
		
		if (exceptions[0] != null) 
		{
			handle(exceptions[0]);
		}
	}

	/** Handles exceptions that occur in Perforce model elements. 
	  * 
	  * @param t exception that should be handled.
	  */
	protected void handle(Throwable t) 
	{
		if (t instanceof InvocationTargetException)
		{
			t = ((InvocationTargetException) t).getTargetException();
		}
		
		IStatus error = null;
		if (t instanceof CoreException) 
		{
			error = ((CoreException) t).getStatus();
		}
		else 
		if (t instanceof TeamException) 
		{
			error = ((TeamException) t).getStatus();
		}
		else 
		{
			error = new Status(Status.ERROR, PerforceUIPlugin.ID, 1, 
				Policy.bind("simpleInternal"), t);                                     //$NON-NLS-1$
		}
	
		Shell shell = new Shell(Display.getDefault());
	
		if (error.getSeverity() == IStatus.INFO)
		{
			MessageDialog.openInformation(shell, Policy.bind("information"),           //$NON-NLS-1$
			                              error.getMessage());
		}
		else
		{
			ErrorDialog.openError(
				shell, 
				Policy.bind("exception"),                                              //$NON-NLS-1$ 
				error.getMessage(), 
				error, 
				IStatus.ERROR);
		}
		
		shell.dispose();

		// Let's log non-team exceptions
		if (!(t instanceof TeamException)) 
		{
			PerforceUIPlugin.log(error);
		}
	}
			
	/**
	  * Concrete action enablement code.
	  * Subclasses must implement.
	  * 
	  * @return whether the action is enabled
 	  * @throws PerforceException if an error occurs during enablement detection
	  */
	protected abstract boolean isEnabled() 
		throws PerforceException;	

	/** Returns the selected resources.
	  * 
	  * @return the selected resources
	  */
	protected IResource[] getSelectedResources()
	{
		ArrayList resources = null;
		if (!selection.isEmpty()) 
		{
			resources = new ArrayList();
			for (Iterator elements = getSelection().iterator(); elements.hasNext();)
			{
				Object next = elements.next();
				if (next instanceof IResource) 
				{
					resources.add(next);
					continue;
				}
				else
				if (next instanceof IAdaptable) 
				{
					IAdaptable a = (IAdaptable) next;
					Object adapter = a.getAdapter(IResource.class);
					if (adapter instanceof IResource) 
					{
						resources.add(adapter);
						continue;
					}
				}
			}
		}
		
		if (resources != null && !resources.isEmpty()) 
		{
			return (IResource[]) resources.toArray(new IResource[resources.size()]);
		}
		
		return new IResource[0];
	}
	
	/** Returns the selected remote files.
	  *  
	  * @return array of remote files.
	  * TODO: Refactor to utility class?
	  */
	protected RemoteFile[] getSelectedRemoteFiles()
	{
		ArrayList resources = null;
		if (!getSelection().isEmpty())
		{
			resources = new ArrayList();
			for (Iterator elements = getSelection().iterator(); elements.hasNext();)
			{
				Object next = elements.next();
				if (next instanceof RemoteFile)
				{
					resources.add(next);
					continue;
				}
				else
				if (next instanceof IAdaptable)
				{
					IAdaptable a = (IAdaptable) next;
					Object adapter = a.getAdapter(RemoteFile.class);
					if (adapter instanceof RemoteFile)
					{
						resources.add(adapter);
						continue;
					}					
				}
			}
		}
		
		if (resources != null && !resources.isEmpty())
		{
			return (RemoteFile[]) resources.toArray(new RemoteFile[resources.size()]);
		}
		
		return new RemoteFile[0];
	}
	

	/** Call this method to retrieve the currently selected RemoteFolders.
	  * 
	  * @return the currently selected remote folders.
	  */
	protected RemoteFolder[] getSelectedRemoteFolders()
	{
		ArrayList resources = null;
		if (!getSelection().isEmpty())
		{
			resources = new ArrayList();
			for (Iterator elements = getSelection().iterator(); elements.hasNext();)
			{
				Object next = elements.next();			
				if (next instanceof RemoteFolder)
				{
					resources.add(next);
					continue;
				}
				else
				if (next instanceof IAdaptable)
				{
					IAdaptable a = (IAdaptable) next;
					Object adapter = a.getAdapter(RemoteFolder.class);
					if (adapter instanceof RemoteFolder)
					{
						resources.add(adapter);
						continue;
					}
				}
			}
		}
		
		if (resources != null && !resources.isEmpty())
		{
			return (RemoteFolder[]) resources.toArray(new RemoteFolder[resources.size()]);
		}
		
		return new RemoteFolder[0];
	}

	/**
	 * Convenience method that maps the selected resources to their providers.
	 * The returned Hashtable has keys which are ITeamProviders, and values
	 * which are Lists of IResources that are shared with that provider.
	 * 
	 * @return a hashtable mapping providers to their selected resources
	 */
	protected Map getProviderMapping() 
	{
		return getProviderMapping(getSelectedResources());
	}
	
	/**
	 * Convenience method that maps the given resources to their providers.
	 * The returned Hashtable has keys which are ITeamProviders, and values
	 * which are Lists of IResources that are shared with that provider.
	 * 
	 * @return a hashtable mapping providers to their resources
	 */
	protected Map getProviderMapping(IResource[] resources) 
	{
		HashMap result = new HashMap();
		for (int i = 0; i < resources.length; i++) 
		{
			RepositoryProvider provider = 
				RepositoryProvider.getProvider(resources[i].getProject());
			List list = (List) result.get(provider);
			if (list == null) 
			{
				list = new ArrayList();
				result.put(provider, list);
			}
			list.add(resources[i]);
		}
		return result;
	}
}
